package com.zan.service.impl;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zan.dao.ProductImgMapper;
import com.zan.dao.ProductMapper;
import com.zan.dao.ProductParamsMapper;
import com.zan.dao.ProductSkuMapper;
import com.zan.entity.*;
import com.zan.service.ProductService;
import com.zan.utils.PageHelper;
import com.zan.utils.StringUtils;
import com.zan.vo.ResStatus;
import com.zan.vo.ResultVo;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import tk.mybatis.mapper.entity.Example;

import javax.swing.text.Highlighter;
import java.io.IOException;
import java.util.*;

@Service
public class ProductServiceImpl implements ProductService {

    @Autowired
    private ProductMapper productMapper;

    @Autowired
    private ProductImgMapper productImgMapper;

    @Autowired
    private ProductSkuMapper productSkuMapper;

    @Autowired
    private ProductParamsMapper productParamsMapper;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    private ObjectMapper objectMapper = new ObjectMapper();

    @Autowired
    private RestHighLevelClient restHighLevelClient;

    @Override
    public ResultVo listRecommendProducts() {
        List<ProductVo> productVos = productMapper.selectRecommendProducts();
        //productImgMapper这个不需要了，因为在xml中写了调用ProductImgMapper中的查找图片方法
        return new ResultVo(ResStatus.OK.code, "success", productVos);
    }

    @Override
    @Transactional(propagation = Propagation.SUPPORTS)
    public ResultVo getProductBasicInfo(String productId) {
        try {
            // 1.根据商品id查询Redis
            String productInfo = (String) stringRedisTemplate.boundHashOps("products").get(productId);
            // 2.如果Redis中查询到了商品信息，则直接返回给控制器
            if (productInfo != null) {
                Product product = objectMapper.readValue(productInfo, Product.class);

                //从redis中查询此商品的图片
                String imgsStr = (String) stringRedisTemplate.boundHashOps("productImgs").get(productId);
                //将字符串转换成集合 - 需要用到ObjectMapper中的JavaType
                JavaType javaType1 = objectMapper.getTypeFactory().constructParametricType(ArrayList.class, ProductImg.class);
                List<ProductImg> productImgs = objectMapper.readValue(imgsStr, javaType1);

                //从redis中查询此商品的套餐
                String skusStr = (String) stringRedisTemplate.boundHashOps("productSkus").get(productId);
                JavaType javaType2 = objectMapper.getTypeFactory().constructParametricType(ArrayList.class, ProductSku.class);
                List<ProductSku> productSkus = objectMapper.readValue(skusStr, javaType2);

                //封装商品、图片及套餐
                HashMap<String, Object> basicInfo = new HashMap<>();
                basicInfo.put("product", product);
                basicInfo.put("productImgs", productImgs);
                basicInfo.put("productSkus", productSkus);

                return new ResultVo(ResStatus.OK.code, "success", basicInfo);
            } else {
                // 3.如果Redis中没有数据，说明是第一次查询，那么就需要添加，从数据库去拿
                // 3.1 商品基本信息
                Example example = new Example(Product.class);
                Example.Criteria criteria = example.createCriteria();
                criteria.andEqualTo("productId", productId);
                criteria.andEqualTo("productStatus", 1); //状态为1表示上架商品
                List<Product> products = productMapper.selectByExample(example);
                if (products.size() > 0) {
                    // 4.说明查找到了商品信息，将从数据库查询到的数据写入到Redis中
                    Product product = products.get(0);
                    String jsonStr = objectMapper.writeValueAsString(product);
                    stringRedisTemplate.boundHashOps("products").put(productId, jsonStr);

                    // 3.2 根据商品id查询商品图片
                    Example example1 = new Example(ProductImg.class);
                    Example.Criteria criteria1 = example1.createCriteria();
                    criteria1.andEqualTo("itemId", productId);
                    List<ProductImg> productImgs = productImgMapper.selectByExample(example1);
                    stringRedisTemplate.boundHashOps("productImgs").put(productId, objectMapper.writeValueAsString(productImgs));

                    // 3.3 根据商品id查询商品套餐
                    Example example2 = new Example(ProductSku.class);
                    Example.Criteria criteria2 = example2.createCriteria();
                    criteria2.andEqualTo("productId", productId);
                    criteria2.andEqualTo("status", 1);
                    List<ProductSku> productSkus = productSkuMapper.selectByExample(example2);
                    stringRedisTemplate.boundHashOps("productSkus").put(productId, objectMapper.writeValueAsString(productSkus));

                    HashMap<String, Object> basicInfo = new HashMap<>();
                    basicInfo.put("product", products.get(0));
                    basicInfo.put("productImgs", productImgs);
                    basicInfo.put("productSkus", productSkus);
                    return new ResultVo(ResStatus.OK.code, "success", basicInfo);
                }
            }
        } catch (JsonMappingException e) {
            throw new RuntimeException(e);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
        return new ResultVo(ResStatus.NO.code, "查询的商品不存在", null);
    }

    @Override
    public ResultVo getProductParamsById(String productId) {
        Example example = new Example(ProductParams.class);
        Example.Criteria criteria = example.createCriteria();
        criteria.andEqualTo("productId", productId);
        List<ProductParams> productParams = productParamsMapper.selectByExample(example);
        if (productParams.size() > 0) {
            return new ResultVo(ResStatus.OK.code, "success", productParams.get(0));
        } else {
            return new ResultVo(ResStatus.NO.code, "此商品可能为三无产品", null);
        }
    }

    @Override
    public ResultVo getProductsByCategoryId(int categoryId, int pageNum, int limit) {
        //1.查询分页数据
        int start = (pageNum - 1) * limit;
        List<ProductVo2> productVo2s = productMapper.selectProductsByCategoryId(categoryId, start, limit);
        //2.查询当前类别下的商品总记录数
        Example example = new Example(Product.class);
        Example.Criteria criteria = example.createCriteria();
        criteria.andEqualTo("categoryId", categoryId);
        int count = productMapper.selectCountByExample(example);
        //3.计算总页数
        int pageCount = count % limit == 0 ? count / limit : count / limit + 1;
        //4.封装返回数据
        PageHelper<ProductVo2> pageHelper = new PageHelper<ProductVo2>(count, pageCount, productVo2s);

        return new ResultVo(ResStatus.OK.code, "success", pageHelper);
    }

    @Override
    public ResultVo getProductBrandByCategoryId(int categoryId) {
        List<String> strings = productMapper.selectBrandByCategoryId(categoryId);
        return new ResultVo(ResStatus.OK.code, "success", strings);
    }

    @Override
    public ResultVo searchProduct(String keyword, int pageNum, int limit) {
        /*//1.查询搜索结果
        keyword = "%" + keyword + "%";
        int start = (pageNum - 1) * limit;
        List<ProductVo2> productVo2s = productMapper.selectProductsByKeyWord(keyword, start, limit);
        //2.查询总记录数
        Example example = new Example(Product.class);
        Example.Criteria criteria = example.createCriteria();
        criteria.andLike("productName", keyword);
        int count = productMapper.selectCountByExample(example);
        //3.计算总页数
        int pageCount = count % limit == 0 ? count / limit : count / limit + 1;
        //4.封装，返回数据
        PageHelper<ProductVo2> pageHelper = new PageHelper<>(count, pageCount, productVo2s);
        return new ResultVo(ResStatus.OK.code, "success", pageHelper);*/

        /**
         * 使用ES进行查询
         */
        ResultVo resultVo = null;
        try {
            int start = (pageNum - 1) * limit;

            SearchRequest searchRequest = new SearchRequest("fmmallproductsindex");
            SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
            //查询条件
            searchSourceBuilder.query(QueryBuilders.multiMatchQuery(keyword, "productName", "productSkuName"));

            //分页条件
            searchSourceBuilder.from(start);
            searchSourceBuilder.size(limit);

            //高亮显示
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            HighlightBuilder.Field field1 = new HighlightBuilder.Field("productName");
            HighlightBuilder.Field field2 = new HighlightBuilder.Field("productSkuName");
            highlightBuilder.field(field1);
            highlightBuilder.field(field2);
            highlightBuilder.preTags("<label style='color:red'>");
            highlightBuilder.postTags("</label>");
            searchSourceBuilder.highlighter(highlightBuilder);
            searchRequest.source(searchSourceBuilder);
            //执行检索
            SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            //封装查询结果
            SearchHits searchHits = searchResponse.getHits();
            //获取总记录数
            long count = searchHits.getTotalHits().value; // 21亿条还是有可能，但是21亿页不大可能，没有这么多
            Iterator<SearchHit> iterator = searchHits.iterator();
            List<Product4ES> products = new ArrayList<>();
            while (iterator.hasNext()) {
                SearchHit searchHit = iterator.next(); //代表一条数据
                Product4ES product4ES = objectMapper.readValue(searchHit.getSourceAsString(), Product4ES.class);
                //获取高亮字段
                Map<String, HighlightField> highlightFields = searchHit.getHighlightFields();
                //productName
                HighlightField highlightField1 = highlightFields.get("productName");
                if (highlightField1 != null) {
                    String highLightProductName = StringUtils.removeSquareBrackets(Arrays.toString(highlightField1.getFragments()));
                    product4ES.setProductName(highLightProductName);
                }

                //productSkuName
                HighlightField highlightField2 = highlightFields.get("productSkuName");
                if (highlightField2 != null) {
                    String highLightSkuName = StringUtils.removeSquareBrackets(Arrays.toString(highlightField2.getFragments()));
                    product4ES.setProductName(highLightSkuName);
                }

                products.add(product4ES);
            }

            //3.计算总页数
            int pageCount = (int) (count % limit == 0 ? count / limit : count / limit + 1); // 根据项目业务来用什么类型
            //4.封装，返回数据
            PageHelper<Product4ES> pageHelper = new PageHelper<>((int) count, pageCount, products);

            resultVo = new ResultVo(ResStatus.OK.code, "success", pageHelper);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return resultVo;
    }

    @Override
    public ResultVo getProductBrandByKeyWord(String keyword) {
        keyword = "%" + keyword + "%";
        List<String> brands = productMapper.selectBrandByKeyWord(keyword);
        return new ResultVo(ResStatus.OK.code, "success", brands);
    }
}
